import React, { useState, useEffect, useRef } from 'react';
import ReactDOM from 'react-dom';
import { MY_ENDLESS_LIST } from './config';
import './infiniteScroll.css';

const THRESHOLD = 15;

const SlidingWindowScrollHook = (props) => {
  const [start, setStart] = useState(0);
  const [end, setEnd] = useState(THRESHOLD);
  const [observer, setObserver] = useState(null);

  const topEle = useRef();
  const bottomEle = useRef();

  useEffect(() => {
    // 定义观察
    intiateScrollObserver();

    return () => {
      // 放弃观察
      resetObserver();
    };
  }, [end]); // 因为[end] 是同步刷新，这里用一个就行了

  // 定义观察
  const intiateScrollObserver = () => {
    const opts = {
      root: null, // 用于观察的根元素，默认是浏览器的视口，也可以指定具体元素，指定元素的时候用于观察的元素必须是指定元素的子元素
      rootMargin: '0px', // 用来扩大或者缩小视窗的的大小，使用css的定义方法，10px 10px 30px 20px表示top、right、bottom 和 left的值
      threshold: 0.1, // 决定了什么时候触发回调函数。它是一个数组，每个成员都是一个门槛值，默认为[0]，即交叉比例（intersectionRatio）达到0时触发回调函数。用户可以自定义这个数组。比如，[0, 0.25, 0.5, 0.75, 1]就表示当目标元素 0%、25%、50%、75%、100% 可见时，会触发回调函数。
    };

    const Observer = new IntersectionObserver(callback, opts);

    // 分别观察开头和结尾元素
    if (topEle.current) {
      Observer.observe(topEle.current);
    }
    if (bottomEle.current) {
      Observer.observe(bottomEle.current);
    }

    // 设置初始值
    setObserver(observer);
  };

  const updateState = (newStart, newEnd) => {
    if (start !== newStart || end !== newEnd) {
      setStart(newStart);
      setEnd(newEnd);
    }
  };

  // 停止滚动放弃观察
  const resetObserver = () => {
    observer?.unobserver(topEle.current);
    observer?.unobserver(bottomEle.current);
  };

  // 渲染时处理头尾ref
  const getReference = (index, isLastIndex) => {
    if (index === 0) return topEle;
    if (isLastIndex) return bottomEle;
    return null;
  };

  // 交叉观察的具体回调，观察每个节点，并对实时头尾元素索引处理
  const callback = (entries, observer) => {
    entries.forEach((entry, index) => {
      console.log('entry: ', entry);
      // entry 对象的每个属性
      // time: 可见性发生变化的时间，是一个高精度时间戳，单位为毫秒
      // target: 被观察的目标元素，是一个 DOM 节点对象
      // rootBounds: 根元素的矩形区域的信息，getBoundingClientRect()方法的返回值，如果没有根元素（即直接相对于视口滚动），则返回null
      // boundingClientRect: 目标元素的矩形区域的信息
      // intersectionRect: 目标元素与视口（或根元素）的交叉区域的信息
      // intersectionRatio: 目标元素的可见比例，即intersectionRect占boundingClientRect的比例，完全可见时为1，完全不可见时小于等于0
      // isIntersecting: 是否进入可视区域

      const listLength = props.list.length;
      // 向下滚动，刷新数据
      if (entry.isIntersecting && entry.target.id === 'bottom') {
        const maxStartIndex = listLength - 1 - THRESHOLD; // 当前头部的索引
        const maxEndIndex = listLength - 1; // 当前尾部的索引
        const newEnd = end + 10 <= maxEndIndex ? end + 10 : maxEndIndex; // 下一轮增加尾部
        const newStart = end - 5 <= maxStartIndex ? end - 5 : maxStartIndex; // 在上一轮的基础上计算头部
        updateState(newStart, newEnd);
      }
      // 向上滚动，刷新数据
      if (entry.isIntersecting && entry.target.id === 'top') {
        const newEnd =
          end === THRESHOLD
            ? THRESHOLD
            : end - 10 > THRESHOLD
            ? end - 10
            : THRESHOLD; // 向上滚动尾部元素索引不得小于15
        let newStart = start === 0 ? 0 : start - 10 > 0 ? start - 10 : 0; // 头部元素索引最小值为0
        updateState(newStart, newEnd);
      }
    });
  };

  const { list, height } = props;
  const updatedList = list.slice(start, end);
  console.log(updatedList.length);

  const lastIndex = updatedList.length - 1;

  return (
    <ul style={{ position: 'relative' }}>
      {updatedList.map((item, index) => {
        const top = height * (index + start) + 'px';
        const refVal = getReference(index, index === lastIndex);
        const id = index === 0 ? 'top' : index === lastIndex ? 'bottom' : '';
        return (
          <li
            className="li-card"
            key={item.key}
            style={{ top }}
            ref={refVal}
            id={id}
          >
            {item.value}
          </li>
        );
      })}
    </ul>
  );
};

function App() {
  return (
    <>
      <div className="App">
        <h1>15个元素实现无限滚动</h1>
        <SlidingWindowScrollHook list={MY_ENDLESS_LIST} height={195} />
      </div>
    </>
  );
}

ReactDOM.render(<App />, document.getElementById('root'));
